package Extras;

import java.util.Iterator;
import java.util.NoSuchElementException;

public class LinkedList {

    private Nodo frente;
    private Nodo last;
    private Class clase;
    private int cantNodos;

    public LinkedList() {
        cantNodos = 0;
    }

    private class Iterador implements Iterator {

        private Nodo actual;

        public Iterador() {
        }

        public boolean hasNext() {
            if (frente == null) {
                return false;
            }
            if (actual != null && (actual.getNext() == frente || actual.getNext() == null)) {
                return false;
            }
            return true;
        }

        public Comparable next() {
            if (!hasNext()) {
                throw new NoSuchElementException("No hay elementos por recorrer");
            }

            if (actual == null) {
                actual = frente;
            } else {
                actual = actual.getNext();
            }
            return actual.getInfo();
        }

        public void remove() {
        }
    }

    public Iterator iterator() {
        return new Iterador();
    }

    private boolean esHomogeneo(Comparable a) {
        if (a == null) {
            return false;
        }

        if (a.getClass().getSuperclass() != clase && a.getClass() != clase && frente != null) {
            return false;
        }

        return true;
    }

    public boolean add(Comparable a) {
        Nodo nuevo = null;
        if (!esHomogeneo(a)) {
            return false;
        }
        if (frente == null) {
            nuevo = new Nodo(null, null, a);
            frente = nuevo;
            last = nuevo;
            cantNodos++;
            if (clase == null) {
                if (a.getClass().getSuperclass() == Object.class) {
                    clase = a.getClass();
                } else {
                    clase = a.getClass().getSuperclass();
                }
            }
            return true;
        } else {
            nuevo = new Nodo(frente, last, a);
            frente = nuevo;
            nuevo.getNext().setPrevious(frente);
            last.setNext(frente);
            cantNodos++;
            return true;
        }
    }

    public boolean addFirst(Comparable a) {
        Nodo nuevo;
        if (!esHomogeneo(a)) {
            return false;
        }
        if (frente == null) {
            nuevo = new Nodo(frente, last, a);
            frente = nuevo;
            last = nuevo;
            cantNodos++;
            return true;
        } else {
            nuevo = new Nodo(frente, last, a);
            frente = nuevo;
            nuevo.getNext().setPrevious(frente);
            last.setNext(frente);
            cantNodos++;
            return true;
        }
    }

    public boolean addLast(Comparable a) {
        Nodo nuevo = null;
        if (!esHomogeneo(a)) {
            return false;
        }
        if (frente == null) {
            nuevo = new Nodo(null, null, a);
            frente = nuevo;
            last = nuevo;
            cantNodos++;
            return true;
        } else {
            nuevo = new Nodo(frente, last, a);
            last = nuevo;
            nuevo.getPrevious().setNext(last);
            frente.setPrevious(last);
            cantNodos++;
            return true;
        }
    }

    public boolean addinOrder(Comparable a) {
        if (!esHomogeneo(a)) {
            return false;
        }
        if (frente == null) {
            add(a);
            return true;
        }
        Nodo nuevo = new Nodo(null, null, a);
        Nodo p = frente;

        for (int i = 0; i < Size(); i++) {
            if (a.compareTo(p.getInfo()) <= 0) {
                nuevo.setNext((p));
                if (frente == last) {
                    p.setPrevious(nuevo);
                    frente = nuevo;
                    p.setNext(frente);
                    nuevo.setPrevious(last);
                } else {
                    if (last.getNext() == p) {
                        p.setPrevious(nuevo);
                        frente = nuevo;
                        last.setNext(frente);
                        nuevo.setPrevious(last);
                    } else {
                        nuevo.setPrevious(p.getPrevious());
                        p.getPrevious().setNext(nuevo);
                        p.setPrevious(nuevo);
                    }
                }
                cantNodos++;
                return true;
            }

            p = p.getNext();
        }
        this.addLast(a);
        return true;

    }

    public void clear() {
        frente = null;
        last = null;
    }

    public boolean remove(Comparable a) {
        if (isEmpty() || !esHomogeneo(a)) {
            return false;
        }
        Nodo p = frente;

        for (int i = 0; i < Size(); i++) {
            if (p.getInfo().compareTo(a) == 0) {

                if (frente == last) {
                    frente = null;
                    last = null;
                    cantNodos--;
                    return true;
                } else if (p.getPrevious() == last) {
                    frente = p.getNext();
                    /*p.getNext().setPrevious(p.getPrevious());
                     p.getPrevious().setNext(p.getNext());*/
                } else if (p.getNext() == frente) {
                    last = p.getPrevious();
                    /*p.getNext().setPrevious(p.getPrevious());
                     p.getPrevious().setNext(p.getNext());*/
                }
                p.getNext().setPrevious(p.getPrevious());
                p.getPrevious().setNext(p.getNext());
                p.setNext(null);
                p.setPrevious(null);
                cantNodos--;
                return true;
            }
            p = p.getNext();
        }

        return false;
    }

    public Comparable removeFirst() {

        if (isEmpty()) {
            return null;
        }
        Nodo p = frente;
        if (frente == last) {
            frente = null;
            last = null;
            cantNodos--;
            return p.getInfo();
        } else {

            frente = p.getNext();
            p.getNext().setPrevious(last);
            cantNodos--;
            p.setNext(null);
            p.setPrevious(null);
            return p.getInfo();
        }
    }

    public Comparable removeLast() {
        if (isEmpty()) {
            return null;
        }
        Nodo p = last;
        if (frente == last) {
            frente = null;
            last = null;
            cantNodos--;
            return p.getInfo();
        } else {
            last = p.getPrevious();
            p.getPrevious().setNext(frente);
            cantNodos--;
            p.setNext(null);
            p.setPrevious(null);
            return p.getInfo();
        }
    }

    public boolean remove() {
        if (isEmpty()) {
            return false;
        }
        Nodo p = frente;
        if (frente == last) {
            frente = null;
            last = null;
            cantNodos--;
            return true;
        } else {

            frente = p.getNext();
            p.getNext().setPrevious(last);
            cantNodos--;
            p.setNext(null);
            p.setPrevious(null);
            return true;
        }
    }

    public Comparable remove(int indice) {
        if (isEmpty() || indice > Size() - 1) {
            return null;
        }
        Nodo p = frente;
        for (int i = 0; i < Size(); i++) {
            if (i == indice) {
                if (frente == last) {
                    frente = null;
                    last = null;
                    cantNodos--;
                    return p.getInfo();
                } else if (p.getPrevious() == last) {
                    frente = p.getNext();
                } else if (p.getNext() == frente) {
                    last = p.getPrevious();
                }
                p.getNext().setPrevious(p.getPrevious());
                p.getPrevious().setNext(p.getNext());
                p.setNext(null);
                p.setPrevious(null);
                cantNodos--;
                return p.getInfo();
            }
            p = p.getNext();

        }
        return null;
    }

    public Comparable set(int indice, Comparable a) {
        if (isEmpty() || indice > Size() - 1 || !esHomogeneo(a)) {
            return null;
        }
        Nodo p = frente;
        for (int i = 0; i < Size(); i++) {
            if (indice == i) {
                Comparable q = p.getInfo();
                p.setInfo(a);
                return q;
            }
            p = p.getNext();
        }
        return null;
    }

    public Comparable get(int indice) {
        if (isEmpty() || indice > Size() - 1) {
            return null;
        }
        Nodo p = frente;
        for (int i = 0; i < Size(); i++) {
            if (i == indice) {
                return p.getInfo();
            }
            p = p.getNext();
        }
        return null;
    }

    public Comparable getFirst() {
        if (isEmpty()) {
            return null;
        }
        return frente.getInfo();
    }

    public Comparable getLast() {
        if (isEmpty()) {
            return null;
        }
        return last.getInfo();
    }

    public boolean contains(Comparable a) {
        if (isEmpty() || !esHomogeneo(a)) {
            return false;
        }
        Nodo p = frente;
        for (int i = 0; i < Size(); i++) {
            if (p.getInfo().compareTo(a) == 0) {
                return true;
            }
            p = p.getNext();
        }
        return false;

    }

    public Comparable poll() {
        return removeFirst();
    }

    public Comparable pollFirst() {
        return removeFirst();
    }

    public Comparable pollLast() {
        return removeLast();
    }

    public Comparable peek() {
        return getFirst();
    }

    public Comparable peekFirst() {
        return getFirst();
    }

    public Comparable peekLast() {
        return getLast();
    }

    public int indexOf(Comparable a) {
        if (isEmpty() || !esHomogeneo(a)) {
            return -2;
        }
        Nodo p = frente;
        for (int i = 0; i < Size(); i++) {
            if (p.getInfo().compareTo(a) == 0) {
                return i;
            }
            p = p.getNext();
        }
        return -1;
    }

    public boolean offer(Comparable a) {
        return addLast(a);
    }

    public boolean offerLast(Comparable a) {
        return addLast(a);
    }

    public boolean offerFirst(Comparable a) {
        return addFirst(a);
    }

    public boolean isEmpty() {
        if (cantNodos == 0) {
            return true;
        }
        return false;
    }

    public int Size() {
        return cantNodos;
    }


public class Nodo
{

    private Nodo next, previous;
    private Comparable info;

    public Nodo(Nodo n, Nodo p, Comparable x)
    {
	next = n;
	previous = p;
	info = x;
    }

    public Nodo()
    {
    }

    public Comparable getInfo()
    {
	return info;
    }

    public void setInfo(Comparable info)
    {
	this.info = info;
    }

    public Nodo getNext()
    {
	return next;
    }

    public void setNext(Nodo next)
    {
	this.next = next;
    }

    public Nodo getPrevious()
    {
	return previous;
    }

    public void setPrevious(Nodo previous)
    {
	this.previous = previous;
    }

    @Override
    public String toString()
    {
	return info.toString();
    }
}

}
